Merge pull request #462 from knu/liquid-to_xpath

Add a Liquid filter `to_xpath`, which quotes a string for use in XPath expression.

Akinori MUSHA 10 years ago
parent
commit
d378795176

+ 19 - 2
app/concerns/liquid_interpolatable.rb

@@ -43,12 +43,29 @@ module LiquidInterpolatable
43 43
   end
44 44
 
45 45
   require 'uri'
46
-  # Percent encoding for URI conforming to RFC 3986.
47
-  # Ref: http://tools.ietf.org/html/rfc3986#page-12
48 46
   module Filters
47
+    # Percent encoding for URI conforming to RFC 3986.
48
+    # Ref: http://tools.ietf.org/html/rfc3986#page-12
49 49
     def uri_escape(string)
50 50
       CGI.escape(string) rescue string
51 51
     end
52
+
53
+    # Escape a string for use in XPath expression
54
+    def to_xpath(string)
55
+      subs = string.scan(/\G(?:\A\z|[^"]+|[^']+)/).map { |x|
56
+        case x
57
+        when /"/
58
+          %Q{'#{x}'}
59
+        else
60
+          %Q{"#{x}"}
61
+        end
62
+      }
63
+      if subs.size == 1
64
+        subs.first
65
+      else
66
+        'concat(' << subs.join(', ') << ')'
67
+      end
68
+    end
52 69
   end
53 70
   Liquid::Template.register_filter(LiquidInterpolatable::Filters)
54 71
 

+ 18 - 0
spec/concerns/liquid_interpolatable_spec.rb

@@ -1,4 +1,5 @@
1 1
 require 'spec_helper'
2
+require 'nokogiri'
2 3
 
3 4
 describe LiquidInterpolatable::Filters do
4 5
   before do
@@ -36,4 +37,21 @@ describe LiquidInterpolatable::Filters do
36 37
       agent.errors[:options].first.should =~ /not properly terminated/
37 38
     end
38 39
   end
40
+
41
+  describe 'to_xpath' do
42
+    before do
43
+      def @filter.to_xpath_roundtrip(string)
44
+        Nokogiri::XML('').xpath(to_xpath(string))
45
+      end
46
+    end
47
+
48
+    it 'should escape a string for use in XPath expression' do
49
+      [
50
+        %q{abc}.freeze,
51
+        %q{'a"bc'dfa""fds''fa}.freeze,
52
+      ].each { |string|
53
+        @filter.to_xpath_roundtrip(string).should == string
54
+      }
55
+    end
56
+  end
39 57
 end

+ 17 - 6
spec/models/agents/website_agent_spec.rb

@@ -456,7 +456,10 @@ fire: hot
456 456
       before do
457 457
         @event = Event.new
458 458
         @event.agent = agents(:bob_rain_notifier_agent)
459
-        @event.payload = { 'url' => "http://xkcd.com" }
459
+        @event.payload = {
460
+          'url' => 'http://xkcd.com',
461
+          'link' => 'Random',
462
+        }
460 463
       end
461 464
 
462 465
       it "should scrape from the url element in incoming event payload" do
@@ -467,17 +470,25 @@ fire: hot
467 470
       end
468 471
 
469 472
       it "should interpolate values from incoming event payload" do
470
-        @event.payload['title'] = 'XKCD'
471
-
472 473
         lambda {
473
-          @valid_options['extract']['site_title'] = {
474
-            'css' => "#comic img", 'value' => "'{{title}}'"
474
+          @valid_options['extract'] = {
475
+            'from' => {
476
+              'xpath' => '*[1]',
477
+              'value' => '{{url | to_xpath}}'
478
+            },
479
+            'to' => {
480
+              'xpath' => '(//a[@href and text()={{link | to_xpath}}])[1]',
481
+              'value' => '@href'
482
+            },
475 483
           }
476 484
           @checker.options = @valid_options
477 485
           @checker.receive([@event])
478 486
         }.should change { Event.count }.by(1)
479 487
 
480
-        Event.last.payload['site_title'].should == 'XKCD'
488
+        Event.last.payload.should == {
489
+          'from' => 'http://xkcd.com',
490
+          'to' => 'http://dynamic.xkcd.com/random/comic/',
491
+        }
481 492
       end
482 493
     end
483 494
   end